#!/usr/bin/env python
#
# g-DrcomClient-cli , a Novel Command-Line Tool for Drcom-Client
#
#       g-drcomclient-cli-for-mac.py
#
# Copyright (c) 2008, Henry Huang <henry.s.huang@gmail.com>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

import time,sys,os,signal,atexit
from os import listdir,getcwd,path
import socket,md5,fcntl,struct
from operator import xor
import optparse, errno
import uuid,binascii

global conf_file
conf_file='gdrcom.conf'

global log_file
log_file='gdrcom.log'

global msg_file
msg_file= 'gdrcom.msg'

global conf_path
conf_path=getcwd()

global pid_file
pid_file = 'gdrcom.pid'

class drcom_client:

	def get_ifname(self):

		ifname_space=os.popen("/sbin/ifconfig -l").read()
		ifname_start=0
		ifname_name=[]
		while(ifname_start!=-1):
			ifname_end=ifname_space.find(' ',ifname_start+1)
			if ifname_end==-1:
				if ifname_space.find('\n',ifname_start+1):
					ifname=ifname_space[ifname_start+1:ifname_end]
					ifname_name.append(ifname)
				break
			if ifname_start==0:
				ifname=ifname_space[ifname_start:ifname_end]
			else:
				ifname=ifname_space[ifname_start+1:ifname_end]
			ifname_name.append(ifname)
			ifname_start=ifname_end

		ifname_status=os.popen("/sbin/ifconfig | awk '{print $1,$2}'").read()
		for i in range(0,len(ifname_name)):
			ifname_start=ifname_status.find(ifname_name[i])
			if i==len(ifname_name)-1:
				ifname_end=len(ifname_status)
			else:
				ifname_end=ifname_status.find(ifname_name[i+1])
			ifname_region=ifname_status[ifname_start:ifname_end]
			ifname_index=ifname_region.find('RUNNING')
			if ifname_index!=-1 and 'lo' not in ifname_name[i]:
				return ifname_name[i]
				
		tag='Error 01'	
		self.save_log(tag,'No active network card!')
		self.quit()

	def get_ip_addr(self):
		try:
			ip_addr_dec=os.popen("ipconfig getifaddr "+self.ifname).read().replace('\n','')
			
		except:
			tag='Error 03'
			self.save_log(tag,'Can not get your IP address!')
			self.quit()
		return socket.inet_aton(ip_addr_dec)


	def get_mac_addr(self):
		try:
			mac_addr=binascii.unhexlify(hex(uuid.getnode()).replace('0x','').replace('L',''))
		except:
			tag='Error 04'
			self.save_log(tag,'Can not get your MAC address!')
			self.quit()

		return '\x00'*(6-len(mac_addr))+mac_addr

	def read_conf(self):

		pathname=conf_path
		if path.exists(pathname)==False:
			os.mkdir(pathname)
		file_path=path.join(pathname,conf_file)
		list_dir=listdir(pathname)
		if conf_file in list_dir:
			f=file(file_path,'r')
			account_pass=f.read()		
			f.close()
			account_end=account_pass.find(',')
			if account_end==-1:
				self.passwd_flag=False		
				self.account=''
				self.password=''
				return False
			self.account=account_pass[:account_end]
			if account_pass[len(account_pass)-1].isdigit()==True:
				self.password=account_pass[account_end+1:len(account_pass)]
			else:
				self.password=account_pass[account_end+1:len(account_pass)-1]
			self.passwd_flag=True
			return True

		self.passwd_flag=False		
		self.account=''
		self.password=''
		return False		

	def init_conf(self):
		self.BUFFER=512
		self.server_brand='Drco'
		self.server_ip='192.168.168.168'
		self.server_port=61440
		self.ifname=self.get_ifname()
		self.md5_tail='\x14\x00\x07\x0b'
		self.host_ip=self.get_ip_addr()
		self.host_ip_dec=socket.inet_ntoa(self.host_ip)
		self.host_name='\x00'*16
		self.kernel_version='\x00'*12
		self.mac_addr=self.get_mac_addr()
		self.packet_id={
			'client_start_request':'\x01\x00\x00\x04',
			'server_start_response':'\x02\x00\x00\x04',
			'client_login':'\x03\x01',
			'server_login':'\x04\x00\x00\x05\x03',
			'client_keeplive_1':'\xfe',
			'client_keeplive_2':'\xff',
			'server_keeplive':'\x4d\x26',
			'client_end_request':'\x01\x00\x06\x04',
			'server_end_response':'\x02\x00\x06\x04',
			'client_logout':'\x06\x01',
			'server_logout':'\x04\x00\x00\x05\x03',
			}
		self.keep_live_count=0

	def hex_xor(self,xor_oper1,xor_oper2,oper_len):
		xor_result=''
		for i in range(0,oper_len):
			temp=chr(xor(ord(xor_oper1[i]),ord(xor_oper2[i])))
			xor_result=xor_result+temp
		return xor_result

	def show_hex(self,hex_oper):
		for i in range(0,len(hex_oper)):
			print hex(ord(hex_oper[i])),
		print '\n'

	def show_dec(self,dec_oper):
		dec_result=''
		for i in range(0,len(dec_oper)):
			dec_hex=hex(ord(dec_oper[i]))[2:]
			dec_result=dec_result+'0'*(2-len(dec_hex))+dec_hex
		return str(int(dec_result,16))

	def md5_key(self,md5_content):
		md5_temp=md5.new()
		md5_temp.update(md5_content)
		return md5_temp.digest()

	def drcom_login(self):

		try:
			if self.login_flag==[True,False]:
				tag='Error 04'
				self.save_log(tag,'You have already LOGIN!')
				return False
		except:
			self.login_flag=[False,False]						

		self.init_conf()	
		proc_name='client_start_request'
		self.drcom_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
		try:	
			self.drcom_sock.bind((self.host_ip_dec,self.server_port))
			self.drcom_sock.settimeout(1.0)
		except:
			tag='Error 05'
			self.save_log(tag,'Fail to bind your address!')
			return False

		send_data=self.packet_id[proc_name]+'\x01'+'\x00'*15
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))		
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
			
		except:
			tag='Error 06'
			self.save_log(tag,'Connection lost when login [request] !')
			return False		
			
		self.service_identifier=recv_data[0][4:8]
		proc_name='client_login'
		data_head=self.packet_id[proc_name]+'\x06\x1f'
		md5_content=self.packet_id[proc_name]+self.service_identifier+self.password
		self.first_md5=self.md5_key(md5_content)
		NULL_1='\x00'*25+'\x18\x01'
		mac_length=len(self.mac_addr)
		mac_xor=self.hex_xor(self.mac_addr,self.first_md5,mac_length)
		md5_content='\x01'+self.password+self.service_identifier+'\x00'*4
		second_md5=self.md5_key(md5_content)
		NULL_2='\x00'*12
		data_front=data_head+self.first_md5+self.account+NULL_1+\
			mac_xor+second_md5+'\x02'+\
			self.host_ip+NULL_2
		third_md5=self.md5_key(data_front)[0:8]
		NULL_3='\x00'*34+'\x02'+'\x00'*3+'\x04'+'\x00'*7+'\x02'+'\x00'*3
		NULL_4='\x00'*(16*7+4)+'\x01\x00\x01\x08'+'\x00'*8
		send_data=data_front+third_md5+'\x00'*5+\
			self.host_name+NULL_3+self.kernel_version+NULL_4
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			tag='Error 06'
			self.save_log(tag,'Connection lost when login [response] !')
			return False

		if len(recv_data[0])==5:
			tag='Login Failed!'
			self.save_log(tag,'Wrong account name or password!')
			self.login_flag=[False,False]
			return False
		
		self.login_flag=[True,False]

		cost_time=recv_data[0][6]+recv_data[0][5]
		cost_traffic=recv_data[0][12]+recv_data[0][11]+recv_data[0][10]+recv_data[0][9]
		cost_money=recv_data[0][14]+recv_data[0][13]
		tag='Login successful!'
		self.service_tail=recv_data[0][23:39]
		self.show_cost(cost_time,cost_traffic,cost_money,tag)

		recv_data=self.drcom_sock.recvfrom(self.BUFFER)

	def drcom_logout(self):
		try:
			if self.login_flag==[False,True]:
				tag='Error 07'
				self.save_log(tag,'You have already LOGOUT!')
				return False
			if self.login_flag==[False,False]:
				tag='Error 08'
				self.save_log(tag,'You should LOGIN first!')
				return False
		except:
			tag='Error 08'
			self.save_log(tag,'You should LOGIN first!')
			return False

		proc_name='client_end_request'
		send_data=self.packet_id[proc_name]+'\x01'+'\x00'*15
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			tag='Error 09'
			self.save_log(tag,'Connection lost when logout [request] !')
			return False

		if recv_data[0][0]=='\x4d':
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)

		self.service_identifier=recv_data[0][4:8]
		proc_name='client_logout'
		data_head=self.packet_id[proc_name]+'\x06\x1f'
		md5_content=self.packet_id[proc_name]+self.service_identifier+self.password
		fifth_md5=self.md5_key(md5_content)
		NULL_1='\x00'*27
		mac_xor=self.hex_xor(self.mac_addr,fifth_md5,len(self.mac_addr))
		send_data=self.packet_id[proc_name]+'\x00\x1f'+fifth_md5+self.account+\
			NULL_1+mac_xor+self.service_tail

		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			tag='Error 09'
			self.save_log(tag,'Connection lost when logout [response] !')
			return False	

		if recv_data[0][0]=='\x4d':
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)

		if recv_data[0][:4]!='\x04\x00\x00\x05':
			tag='Logout failed!'
			self.save_log(tag,'Please check!')
			return False		

		cost_time=recv_data[0][6]+recv_data[0][5]
		cost_traffic=recv_data[0][12]+recv_data[0][11]+recv_data[0][10]+recv_data[0][9]
		cost_money=recv_data[0][14]+recv_data[0][13]
		tag='Logout successful!'
		self.show_cost(cost_time,cost_traffic,cost_money,tag)
		try:
			self.drcom_sock.close()
		except:
			tag='Error 10'
			self.save_log(tag,'Fail to Close Socket!')
			self.quit()
			return False

		self.login_flag=[False,True]

	def keep_live(self):
		if self.login_flag==[True,False]:
			
			if self.keep_live_count>6:
				tag='Error 11'
				self.save_log(tag,'Connection lost when keep_live [response] !')
				self.login_flag=[False,False]
				self.keep_live_count=0
				return False	

			recv_data=self.keep_live_2()
			if len(recv_data)==4:				
				self.keeplive_tag=recv_data[2:]
				self.keep_live_1()
				self.keep_live_count=0
			return True
		else:
			return False
	
	def keep_live_2(self):
		proc_name='client_keeplive_2'
		send_data=self.packet_id[proc_name]+self.first_md5+'\x00'*3+self.service_tail
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
		except:
			tag='Error 11'
			self.save_log(tag,'Connection lost when keep_live [request] !')
			self.quit()
		try:		
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			recv_data=['']
			self.keep_live_count=self.keep_live_count+1			
		
		return recv_data[0]
	
	def keep_live_1(self):
		proc_name='client_keeplive_1'
		md5_content=self.packet_id[proc_name]+self.keeplive_tag[0]+'\x01'+\
			self.md5_tail+self.keeplive_tag
		fourth_md5=self.md5_key(md5_content)
		send_data=self.packet_id[proc_name]+self.keeplive_tag[0]+'\x01'\
			+fourth_md5+'\xff'+self.service_tail
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
		except:
			tag='Error 11'
			self.save_log(tag,'Connection lost when keep_live [request] !')
			self.quit()

	def password_save(self):
		if self.pwd_save_flag=='Y' or pwd_save_flag=='':
			if len(self.account)==0 and len(self.password)==0:
				f=file(path.join(conf_path,conf_file),'w')
				f.write('')
				f.close()
			else:
				f=file(path.join(conf_path,conf_file),'w')
				f.write(self.account+','+self.password)
				f.close()
		else:
			f=file(path.join(conf_path,conf_file),'w')
			f.write('')
			f.close()

	
	def __init__(self):
		
		self.read_conf()
	        atexit.register(unlink)
	        f = open(path.join(conf_path,pid_file), "w")
        	f.write("%d" % os.getpid())
        	f.close()

	        f = open(path.join(conf_path,msg_file), "w")
        	f.close()

		self.login_flag=[False,False]
		self.command()
        	while True: 
			if self.login_flag==[True,False]:
				self.keep_live()
				time.sleep(20)
			continue

	def command(self):
		signal.signal(signal.SIGUSR1, lambda *args: self.drcom_login())
        	signal.signal(signal.SIGUSR2, lambda *args: self.drcom_logout())
        	signal.signal(signal.SIGQUIT, lambda *args: self.quit())	

	def show_cost(self,cost_time,cost_traffic,cost_money,tag):

		info='Used '+self.show_dec(cost_time)+' Min, '+self.show_dec(cost_traffic)+\
			' KB'+'\n'+'Balance '
		
		if len(str(self.show_dec(cost_money)))==4:
			info=info+self.show_dec(cost_money)[0:2]+'.'+self.show_dec(cost_money)[2:4]+' yuan.'
		if len(str(self.show_dec(cost_money)))==3:
			info=info+self.show_dec(cost_money)[0:1]+'.'+self.show_dec(cost_money)[1:3]+' yuan.'
		if len(str(self.show_dec(cost_money)))==2:
			info=info+'0'+'.'+self.show_dec(cost_money)[0:2]+' yuan.'
		if len(str(self.show_dec(cost_money)))==1:
			info=info+'0'+'.'+'0'+self.show_dec(cost_money)+' yuan.'
		
		self.save_log(tag,info)		
		
    	def quit(self): 
		os.remove(path.join(conf_path,msg_file))
		raise SystemExit

	def save_log(self,tag,info):
		content='-'*30+'\n'+time.ctime()+'\n'+'-'*30+'\n'+tag+'\n'+info+'\n'
		f=file(path.join(conf_path,log_file),'a')
		f.write(content)
		f.close()

		f=open(path.join(conf_path,msg_file),'w')
		f.write(content)
		f.close()
		


def show_msg():
	f = open(path.join(conf_path,msg_file))
	p = f.read()
	f.close()
	print p
	return True


def findpid():
	f = open(path.join(conf_path,pid_file))
	p = f.read()
    	f.close()
    	return int(p)

def unlink():
    	os.unlink(path.join(conf_path,pid_file))

def daemonize():

    	# Do first fork.
    	try: 
        	pid = os.fork() 
        	if pid > 0:
        		sys.exit(0) # Exit first parent.
    	except OSError, e: 
        	sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
        	sys.exit(1)
        
    	# Decouple from parent environment.
    	os.chdir("/") 
    	os.umask(0) 
    	os.setsid() 
    
    	# Do second fork.
    	try: 
        	pid = os.fork() 
        	if pid > 0:
        		sys.exit(0) # Exit second parent.
    	except OSError, e: 
        	sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
        	sys.exit(1)
        
    	# Now I am a daemon!



def main():
   
    	from optparse import OptionParser

    	parser = OptionParser()

    	parser.add_option("--login", dest="signal", action="store_const",
        	                const=signal.SIGUSR1, default=0,
        	                help="Login Dr.COM")
    	parser.add_option("--logout", dest="signal", action="store_const",
        	                const=signal.SIGUSR2, default=0,
        	                help="Logout Dr.COM")
    	parser.add_option("-q", "--quit", dest="signal", action="store_const",
        	                const=signal.SIGQUIT, default=0,
        	                help="Cause the daemon to exit")
    	opts, args = parser.parse_args()
    	if opts.signal:
		try:
        		os.kill(findpid(), opts.signal)

		except:
			raise SystemExit, "No such process. Exiting."
		time.sleep(1.5)
		
		if opts.signal!=signal.SIGQUIT:
			show_msg()
		return

    	if os.path.exists(path.join(conf_path,pid_file)):
        	p = findpid()
        	try:
        		os.kill(p, 0)
			
        	except os.error, detail:
            		if detail.errno == errno.ESRCH:
                		print "Stale pid_file exists.  removing it."
                		os.unlink(path.join(conf_path,pid_file))
				os.remove(path.join(conf_path,msg_file))				

        	else:
			raise SystemExit, "Valid pid_file exists.  Exiting."
    
    	daemonize()
    	app = drcom_client()


if __name__ == '__main__': main()



